package com.cm.dynamic.classloader;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.URLUtil;
import com.cm.dynamic.scan.ModuleClassPathScanner;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack;
import org.apache.ibatis.annotations.Mapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import sun.misc.CompoundEnumeration;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;


/**
 * 类描述 动态加载外部jar包的自定义类加载器
 *
 * @version 1.0
 * @fileName ModuleClassLoader.java
 * @date 2019-06-21 10:22
 */
public class ModuleClassLoader extends URLClassLoader {

    String CLASSPATH_URL_PREFIX = "classpath";

    private static final Logger logger = LoggerFactory.getLogger(ModuleClassLoader.class);

    //保存已经加载过的Class对象
    private Map<String, Class> cacheClassMap = new ConcurrentHashMap<>();

    //保存本类加载器加载的class字节码
    // private Map<String, byte[]> classBytesMap = new ConcurrentHashMap<>();

    //需要注册的spring bean的name集合
    private StringStack registeredBeanName = new StringStack();

    /**
     * 默认的构造
     */
    public ModuleClassLoader() {
        this(new URL[] {});
    }

    /**
     *构造
     * @param urls
     */
    public ModuleClassLoader(URL[] urls) {
        super(urls, ClassUtil.getClassLoader());
    }

    /**
     * 通过basePackage 路径加载ucp和加载类文件
     * @param basePackage
     */
    public void addUcpLoadClass(String basePackage) {
        String[] paths = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
        List<File> waitLoadFiles = new ArrayList<>();
        for (String path : paths) {
            try {
                // 如果是Spring的 classpath 形式
                if (path.startsWith(CLASSPATH_URL_PREFIX)) {
                    Resource[] resources = pathMatchingResourcePatternResolver.getResources(path);
                    for (Resource resource : resources) {
                        File file = resource.getFile();
                        waitLoadFiles.add(file);
                    }
                } else {
                    // 否则则是全路径形式
                    String absolutePath = FileUtil.getAbsolutePath(path);
                    File file = new File(absolutePath);
                    waitLoadFiles.add(file);
                }
            } catch (IOException e) {
                logger.error("类加载器读取资源文件异常!");
                throw new RuntimeException("类加载器读取资源文件异常!", e);
            }
        }
        if (!CollectionUtils.isEmpty(waitLoadFiles)) {
            for (File file : waitLoadFiles) {
                // 加入到当前类加载器的classpath中
                this.addToClassPath(file);
                // 加载进jvm
                this.loadClass(file);
            }
        }
    }

    /**
     * 将file下的jar 和 class文件加入到当前类加载器classpath中
     * @param file
     */
    private void addToClassPath(File file) {
        if (!FileUtil.isEmpty(file)) {
            // 把jar包加入到classpath
            addJar(file);
            // 把class加入到classpath
            addURL(file);
        }
    }

    /**
     * 类加载器加载file下jar包 和 目录下的class文件
     * @param file
     */
    private void loadClass(File file) {
        if (!FileUtil.isEmpty(file)) {
            // 递归获取所有的jar包
            this.loadJarClass(file);
            // 递归获取所有class文件加载进jvm
            this.loadFileClass(file);
        }
    }

    /**
     * 递归获取所有class文件加载进jvm
     */
    private void loadFileClass(File file) {
        String parentAbsolutePath = file.getAbsolutePath();
        List<File> classFiles = loopClassFile(file);
        for (File f : classFiles) {
            try {
                String absolutePath = f.getAbsolutePath();
                String classFilePath = absolutePath.substring(absolutePath.indexOf(parentAbsolutePath) + 1, absolutePath.lastIndexOf(".class"));
                String className = classFilePath.replaceAll("/", ".");
                Class<?> aClass = this.loadClass(className);
                cacheClassMap.put(className, aClass);
            } catch (ClassNotFoundException e) {
                logger.error("动态执行器加载class字节码异常", e);
                throw new RuntimeException("动态执行器加载class字节码异常", e);
            }
        }
    }

    /**
     * 递归获取所有的jar包中的class加载进jvm
     * @param file
     */
    private void loadJarClass(File file) {
        List<File> jarFiles = loopJar(file);
        for (File f : jarFiles) {
            try {
                JarFile jarFile = new JarFile(f);
                //解析jar包每一项
                Enumeration<JarEntry> en = jarFile.entries();
                while (en.hasMoreElements()) {
                    JarEntry je = en.nextElement();
                    String name = je.getName();
                    //这里添加了路径扫描限制
                    if (name.endsWith(".class")) {
                        String className = name.replace(".class", "").replaceAll("/", ".");
                        Class<?> aClass = this.loadClass(className);
                        cacheClassMap.put(className, aClass);
                    }
                }
            } catch (IOException e) {
                logger.error("读取jar包中class异常!");
                throw new RuntimeException("读取jar包中class异常!", e);
            } catch (ClassNotFoundException e) {
                logger.error("动态执行器加载class字节码异常", e);
                throw new RuntimeException("动态执行器加载class字节码异常", e);
            }
        }
    }

    public ModuleClassLoader addJar(File jarFileOrDir) {
        if (isJarFile(jarFileOrDir)) {
            return addURL(jarFileOrDir);
        }
        final List<File> jars = loopJar(jarFileOrDir);
        for (File jar : jars) {
            addURL(jar);
        }
        return this;
    }

    @Override
    public void addURL(URL url) {
        super.addURL(url);
    }

    /**
     * 加载dir目录下所有的class文件到classpath
     * @param dir
     * @return
     */
    public ModuleClassLoader addURL(File dir) {
        super.addURL(URLUtil.getURL(dir));
        return this;
    }


    /**
     * 递归获得Jar文件
     *
     * @param file jar文件或者包含jar文件的目录
     * @return jar文件列表
     */
    private static List<File> loopJar(File file) {
        return FileUtil.loopFiles(file, ModuleClassLoader::isJarFile);
    }

    /**
     * 递归获得class文件
     *
     * @param file jar文件或者包含jar文件的目录
     * @return jar文件列表
     */
    private static List<File> loopClassFile(File file) {
        return FileUtil.loopFiles(file, ModuleClassLoader::isClassFile);
    }

    /**
     * 是否为jar文件
     *
     * @param file 文件
     * @return 是否为jar文件
     * @since 4.4.2
     */
    private static boolean isJarFile(File file) {
        if (false == FileUtil.isFile(file)) {
            return false;
        }
        return file.getPath().toLowerCase().endsWith(".jar");
    }

    /**
     * 是否为class文件
     *
     * @param file 文件
     * @return 是否为class文件
     * @since 4.4.2
     */
    private static boolean isClassFile(File file) {
        if (false == FileUtil.isFile(file)) {
            return false;
        }
        return file.getPath().toLowerCase().endsWith(".class");
    }

    public Enumeration<URL> getResources(String name) throws IOException {
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        if (getParent() != null) {
            tmp[0] = getParent().getResources(name);
        } else {
            tmp[0] = getSystemResources(name);
        }
        tmp[1] = findResources(name);

        return new CompoundEnumeration<>(tmp);
    }

    //重写loadClass方法
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (findLoadedClass(name) == null) {
            return super.loadClass(name);
        } else {
            return cacheClassMap.get(name);
        }

    }

    /**
     * 方法描述 初始化spring bean
     *
     * @method initBean
     *//*
    public void initRegisterSpringBean() {
        String basePackage = "";
        for (Map.Entry<String, Class> entry : cacheClassMap.entrySet()) {
            String className = entry.getKey();
            Class<?> cla = entry.getValue();
            if (cla.getAnnotation(SpringBootApplication.class) != null) {
                basePackage = cla.getPackage().getName();
            }

           *//* if(isSpringBeanClass(cla)){
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(cla);
                BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
                //设置当前bean定义对象是单利的
                beanDefinition.setScope("singleton");

                //将变量首字母置小写
                String beanName = StringUtils.uncapitalize(className);

                beanName =  beanName.substring(beanName.lastIndexOf(".")+1);
                beanName = StringUtils.uncapitalize(beanName);

                if (beanName.endsWith("Mapper")) {
                    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(cla);
                    ((AbstractBeanDefinition) beanDefinition).setBeanClass(MapperFactoryBean.class);
                    ((AbstractBeanDefinition) beanDefinition).setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
                }


                SpringContextUtil.getBeanFactory().registerBeanDefinition(beanName,beanDefinition);
                registeredBean.add(beanName);
                System.out.println("注册bean:"+beanName);
            }*//*
        }
        if (StringUtils.isEmpty(basePackage)) {
            throw new RuntimeException("没有Springboot启动类");
        }
        // 扫描注入Spring容器中
        Set<BeanDefinitionHolder> beanDefinitionHolders = ModuleClassPathScanner.getInstance().doScan(basePackage);
        // 注册到类加载器中registeredBeanName容器中
        if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
            List<String> beanNames = beanDefinitionHolders.stream().map(BeanDefinitionHolder::getBeanName).collect(Collectors.toList());
            registeredBeanName.addAll(beanNames);
        }

    }*/

    //获取当前类加载器注册的bean
    //在移除当前类加载器的时候需要手动删除这些注册的bean
    public List<String> getRegisteredBeanName() {
        return registeredBeanName;
    }


    /**
     * 方法描述 判断class对象是否带有spring的注解
     *
     * @param cla jar中的每一个class
     * @return true 是spring bean   false 不是spring bean
     * @method isSpringBeanClass
     */
    public boolean isSpringBeanClass(Class<?> cla) {
        if (cla == null) {
            return false;
        }
        // 特别的是这个mybaits的mapper类
        if (cla.getSimpleName().endsWith(Mapper.class.getSimpleName())) {
            return true;
        }
        //是否是接口
        if (cla.isInterface()) {
            return false;
        }

        //是否是抽象类
        if (Modifier.isAbstract(cla.getModifiers())) {
            return false;
        }

        if (cla.getAnnotation(Component.class) != null) {
            return true;
        }
        if (cla.getAnnotation(Repository.class) != null) {
            return true;
        }
        if (cla.getAnnotation(Service.class) != null) {
            return true;
        }
        if (cla.getAnnotation(Controller.class) != null) {
            return true;
        }
        if (cla.getAnnotation(RestController.class) != null) {
            return true;
        }


        return false;
    }

}
